﻿using System;
using SoftwareConsulting.BI11.NumericalMethods.DhbFunctionEvaluation;
using SoftwareConsulting.BI11.NumericalMethods.Curves;
using SoftwareConsulting.BI11.NumericalMethods.Statistics;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{
    /// <summary>
    /// Класс распределения Фишера-Типпетта
    /// </summary>
    public sealed class FisherTippettDistribution : ProbabilityDensityFunction
    {	    
        private double _alphaDistributionMiddle;//центр распределения	    
        private double _betaDistributionScale;//масштаб распределения

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="alphaDistributionMiddle">центр распределения</param>
        /// <param name="betaDistributionScale">масштаб распределения</param>
        public FisherTippettDistribution(double alphaDistributionMiddle, double betaDistributionScale)
        {
            if (betaDistributionScale <= 0)
                throw new ArgumentOutOfRangeException("betaDistributionScale");
            _alphaDistributionMiddle = alphaDistributionMiddle;
            _betaDistributionScale = betaDistributionScale;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="histogram">Гистограмма, из которой вытягиваются параметры по наилучшим предположениям</param>
        public FisherTippettDistribution(Histogram h)
        {
            double beta = h.StandardDeviation;
            if (beta < 0)
                throw new ArgumentOutOfRangeException();
            beta *= Math.Sqrt(6) / Math.PI;
            SetParameters(h.Average - 0.5772156649 * beta, beta);
        }

        /// <summary>
        /// Возвращает среднее распределения
        /// </summary>
        public override double Average
        {
            get 
            { 
                return 0.5772156649 * _betaDistributionScale + _alphaDistributionMiddle; 
            }
            set
            {
                Assert.Fail(Assert.Id.Assert00281);//для этого распределения недоступно
            }
        }

        /// <summary>
        /// Задает новые параметры
        /// </summary>        
        /// <param name="alphaDistributionMiddle">центр распределения</param>
        /// <param name="betaDistributionScale">масштаб распределения</param>
        public void SetParameters(double alphaDistributionMiddle, double betaDistributionScale)
        {
            _alphaDistributionMiddle = alphaDistributionMiddle;
            _betaDistributionScale = betaDistributionScale;
        }

        /// <summary>
        /// Возвращает значение функции плотности распределения
        /// </summary>
        /// <param name="x"></param>
        /// <returns>вероятность, нахождения случайной переменной меньше или равной, чем X</returns>
        public override double GetDistributionValue(double x)
        {
            double y = (x - _alphaDistributionMiddle) / _betaDistributionScale;
            if (y < -MathExtended.LargestExponentialArgument)
                return 0;
            y = Math.Exp(-y);
            if (y > MathExtended.LargestExponentialArgument)
                return 1;
            return Math.Exp(-y);
        }

        /// <summary>
        /// Возвращает эксцесс распределения
        /// </summary>
        public override double Kurtosis
        {
            get 
            { 
                return 2.4; 
            }
        }

        /// <summary>
        /// Возвращает название распределения
        /// </summary>
        public override string Name
        {
            get 
            { 
                return "Fisher-Tippett distribution"; 
            }
        }

        /// <summary>
        /// Возвращает/устанавливает параметры распределения
        /// </summary>
        public override double[] Parameters
        {
            get { 
                return new double[] { _alphaDistributionMiddle, _betaDistributionScale }; 
            }
            set 
            { 
                SetParameters(value[0], value[1]); 
            }
        }

        /// <summary>
        /// Возвращает случайное вещественное число в соответствие с распределением
        /// </summary>
        /// <returns></returns>
        public override double GetRandomValue()
        {
            double t;
            while ((t = -Math.Log(this.RandomGenerator.NextDouble())) == 0)
            {
                //нам просто не нужен ноль
            };
            return _alphaDistributionMiddle - _betaDistributionScale * Math.Log(t);
        }

        /// <summary>
        /// Возвращает уклон распределения
        /// </summary>
        public override double Skewness
        {
            get 
            { 
                return 1.3; 
            }
        }

        /// <summary>
        /// Возвращает стандартное отклонение дисперсии распределения
        /// </summary>
        public override double StandardDeviation
        {
            get 
            { 
                return Math.PI * _betaDistributionScale / Math.Sqrt(6); 
            }
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0} ({1:####0.00000},{2:####0.00000})", Name, _alphaDistributionMiddle, _betaDistributionScale );
        }

        /// <summary>
        /// Вычисляет значение, основываясь на функции плотности
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public override double GetValue(double x)
        {
            double y = (x - _alphaDistributionMiddle) / _betaDistributionScale;
            if (y > MathExtended.LargestExponentialArgument)
                return 0;
            y += Math.Exp(-y);
            if (y > MathExtended.LargestExponentialArgument)
                return 0;
            return Math.Exp(-y) / _betaDistributionScale;
        }

        /// <summary>
        /// Возвращает значение распределения и градиент распределения с учетом параметров
        /// </summary>
        /// <param name="x"></param>
        /// <returns>0й элемент - значение распределения, 1й и последующие - градиент распределения</returns>
        public override double[] GetValueAndGradient(double x)
        {
            double[] result = new double[3];
            result[0] = this.GetValue(x);
            double y = (x - _alphaDistributionMiddle) / _betaDistributionScale;
            double dy = Math.Exp(-y) - 1;
            double r = -1 / _betaDistributionScale;
            result[1] = dy * result[0] * r;
            result[2] = result[0] * (y * dy + 1) * r;
            return result;
        }
    }
}
